#define EMBEDED
using System;
using System.Data;
using System.Collections;
using Evaluant.OPath;
using Evaluant.OPath.Expressions;
using Evaluant.Uss.Common;
using Evaluant.Uss.Models;
using SQLObject;

namespace Evaluant.Uss.Sql
{
	class SqlTransformer : OPathVisitor
	{

        #region private class InnerJoin

        private class InnerJoin
		{
			public string Alias = String.Empty;
			public string Table = String.Empty;
			public string Condition = String.Empty;

			public InnerJoin(string table, string alias, string condition)
			{
				Table = table;
				Condition = condition;
				Alias = alias;
			}
        }

        #endregion

        #region private class Clause

        private class Clause
		{
			public string Select = String.Empty;
			public string From = String.Empty;
			public ArrayList InnerJoinsAtrb = new ArrayList();
			public ArrayList InnerJoinsRef = new ArrayList();
			public ArrayList Where = new ArrayList();

			public void Merge(Clause clause)
			{
				if(clause == null)
					return;

				Select += clause.Select;

				From += clause.From;
				InnerJoinsAtrb.AddRange(clause.InnerJoinsAtrb);
				InnerJoinsRef.AddRange(clause.InnerJoinsRef);

				if(clause.Where.Count > 0)
					this.Where.Add("( " + String.Join(" and ", (string[])clause.Where.ToArray(typeof(string))) + " )");
			}

			public override string ToString()
			{
				string result = String.Empty;

				if(Select != String.Empty)
					result += "Select " + Select;
				if(From != String.Empty)
					result += "\nFrom " + From;

#if LINEAR
				for(int i=0; i<InnerJoinsAtrb.Count; i++)
				{
					InnerJoin join = (InnerJoin)InnerJoinsAtrb[i];
					result += String.Format(" INNER JOIN {0} {1} ON {2} ", join.Table, join.Alias, join.Condition);
				}

  				for(int i=0; i<InnerJoinsRef.Count; i++)
				{
					InnerJoin join = (InnerJoin)InnerJoinsRef[i];
					result += String.Format(" INNER JOIN {0} {1} ON {2} ", join.Table, join.Alias, join.Condition);
				}


#endif

#if EMBEDED
				for(int i=0; i<InnerJoinsRef.Count;i++)
				{
					InnerJoin join = (InnerJoin)InnerJoinsRef[i];
					
					string bracket = String.Empty;
					if (i != InnerJoinsRef.Count - 1)
						bracket = "(";

					result += String.Format(" INNER JOIN {0} {1} {2}", bracket, join.Table, join.Alias);
				}

				for(int i=InnerJoinsRef.Count-1; i>=0; i--)
				{
					InnerJoin join = (InnerJoin)InnerJoinsRef[i];

					string bracket = String.Empty;
					if (i != 0)
						bracket = ")";

					result += String.Format(" ON {0} {1}", join.Condition, bracket);
				}

				for(int i=0; i<InnerJoinsAtrb.Count; i++)
				{
					InnerJoin join = (InnerJoin)InnerJoinsAtrb[i];
					result += String.Format(", {0} {1}", join.Table, join.Alias);
					Where.Add(join.Condition);
				}

#endif

#if WHERE
				if(From == String.Empty)
					From += "\nFrom ";

  				for(int i=0; i<InnerJoinsAtrb.Count; i++)
				{
					InnerJoin join = (InnerJoin)InnerJoinsAtrb[i];
					result += String.Format(", {0} {1}", join.Table, join.Alias);
					Where.Add(join.Condition);
				}

				for(int i=0; i<InnerJoinsRef.Count; i++)
				{
					InnerJoin join = (InnerJoin)InnerJoinsRef[i];
					result += String.Format(", {0} {1}", join.Table, join.Alias);
					Where.Add(join.Condition);
				}
#endif

				if(Where.Count > 0)
					result += "\nWhere " + String.Join(" and ", (string[])Where.ToArray(typeof(string)));

				return result;
			}
        }

        #endregion

        #region Members

        // Indexes for table's aliases
		private int _EntityIndex, _ReferenceIndex, _AttributeIndex;
		private bool _LastIsAttribute;
        private ArrayList _LinkEntityIndex;             // stock entity alias number of Entity tables (first and last of each from clause)
        private Stack _EntityType;
        private Stack _EntityJoinTable;
        private ISQLExpression _Result;
        private ISQLExpression _ResultColumn;
        private SqlMappingDataType _MappingDataType;
		private SqlPersistenceEngine _Engine;
        
        private Model _Model;
        private Stack _ExprLevel;   // level in opath : expression(link different path) or path 
        private static readonly int EXPR = 0;
        private static readonly int PATH = 1;

        #endregion

        #region ctor

        public SqlTransformer(Model model, SqlMappingDataType mappingType, IPersistenceEngine engine)
        {
            _Model = model;

            _Result = null;
            _ResultColumn = null;
            _EntityJoinTable = new Stack();

            _MappingDataType = mappingType;
            _EntityIndex = _ReferenceIndex = _AttributeIndex = 1;
            _ExprLevel = new Stack();
            _EntityType = new Stack();
			_Engine = engine as SqlPersistenceEngine;

            _LinkEntityIndex = new ArrayList();
        }

        #endregion

        #region Transform

        public ISQLExpression Transform(OPathQuery query)
		{
			if(query.Path == null)
				throw new ArgumentNullException("query.Path");

			if(query.Path.Identifiers.Count == 0)
                throw new ArgumentNullException("query.Path", "Must have at least one Identifier");

            _ExprLevel.Push(PATH);
            SelectStatement select = (SelectStatement)ConvertToSql(query.Path, true, false, false);

            //result.Select = String.Format("DISTINCT [e{0}].[Type], [e{0}].[Id]", _ResultIndex);

            select.IsDistinct = true;
            select.SelectList.Add(new SelectItem(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Type", String.Empty));
            select.SelectList.Add(new SelectItem(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id", String.Empty));

            return select;
		}

        public ISQLExpression TransformScalar(OPathQuery query)
        {
            if (query.Expression == null)
                throw new ArgumentNullException("query.Expression");

            if (query.Expression.Operands.Count == 0)
                throw new ArgumentNullException("query.Expression", "Must have at least one Operand");

            _ExprLevel.Push(EXPR);
            SelectStatement select = (SelectStatement)ConvertToSql(query.Expression, true, false, false);

            return select;
        }

        #endregion

        #region ConvertToSql

        /// <summary>
		/// Converts the Path to a sql query.
		/// </summary>
		/// <param name="path"></param>
		/// <param name="firstIsType">True if the first identifer is a type.</param>
		/// <param name="lastIsAttribute">True if the latest identifer is an attribute.</param>
		/// <param name="isInConstraint"></param>
		/// <returns></returns>
		private ISQLExpression ConvertToSql(Path path, bool firstIsType, bool lastIsAttribute, bool isInConstraint)
		{
            _ExprLevel.Push(firstIsType ? PATH : _ExprLevel.Peek());

			Identifier ident = null;
			int index = 0;
            bool hasLinkedAttribute = false;
	
            SelectStatement result = new SelectStatement(null);
            Stack tmpEntityType = (Stack)_EntityType.Clone();

			// Process the first element differently if it is a Type (Person) and not a relation/atribute (Children/Age)
			if(firstIsType)
			{
				ident = path.Identifiers[index++];

                //string typeFilter = String.Join("', '", _Model.GetTreeAsArray(ident.Value));
                //result.From += String.Format("Entity e{0} ", currentIndex);
                //result.Where.Add(String.Concat("( [e", currentIndex, "].[Type] in ('", typeFilter, "') )"));

                _EntityJoinTable.Push(new TableSource(null, _Engine.EntityTableName, string.Concat("e", _EntityIndex)));

                InPredicate typeFilter = new InPredicate(new Column(null, String.Concat("e", _EntityIndex), "Type", String.Empty));
                string[] types = _Model.GetTreeAsArray(ident.Value);
                foreach(string type in types)
                    typeFilter.SubQueries.Add(new Constant(type, DbType.AnsiString));

                result.WhereClause.SearchCondition.Add(typeFilter);

                _LinkEntityIndex.Add(_EntityIndex);
                _EntityType.Push(_Model.GetEntity(ident.Value, true).Type);

				if(ident.Constraint != null)
				{
					ident.Constraint.Accept(this);

                    if(_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);

                    if (_ResultColumn != null)
                        ((SelectStatement)result).WhereClause.SearchCondition.Add((ILogicExpression)_ResultColumn);
				}

                if (path.Identifiers.Count == 1 && _EntityJoinTable.Count > 0)
                    result.FromClause.Add((Table)_EntityJoinTable.Pop());

                if (lastIsAttribute)
                    hasLinkedAttribute = true;
			}

			int lastIndex = lastIsAttribute ? path.Identifiers.Count - 1 : path.Identifiers.Count;

			// Process each succeeding Identifier
			for(; index < lastIndex; index++)
			{
				ident = path.Identifiers[index];

//				result.From += String.Format(" INNER JOIN Reference r{0} ON e{1}.Id = r{0}.FK_Parent INNER JOIN Entity e{2} ON r{0}.FK_Child = e{2}.Id", _ReferenceIndex, currentIndex, _EntityIndex);
                //result.InnerJoinsRef.Add(
                //    new InnerJoin(
                //    "Reference",
                //    String.Format("r{0}", _ReferenceIndex), 
                //    String.Format("e{1}.Id = r{0}.FK_Parent", _ReferenceIndex, currentIndex))
                //    );

                //result.InnerJoinsRef.Add(
                //    new InnerJoin(
                //    "Entity",
                //    String.Format("e{0}", _EntityIndex), 
                //    String.Format("r{0}.FK_Child = e{1}.Id", _ReferenceIndex, _EntityIndex))
                //    );

                //result.Where.Add(String.Format("r{0}.Role = '{1}'", _ReferenceIndex++, ident.Value));
                
                //result.FromClause.Add(joinRef);
                //result.FromClause.Add(joinEnt);

                if (_EntityJoinTable.Count == 0)
                {
                    _EntityIndex++;
                    _EntityJoinTable.Push(new TableSource(null, _Engine.EntityTableName, String.Concat("e", _EntityIndex)));
                    _LinkEntityIndex.Add(_EntityIndex);
                }

                JoinedTable joinRef = new JoinedTable((Table)_EntityJoinTable.Pop(), TypeJoinedTable.Inner, new TableSource(null, _Engine.ReferenceTableName, String.Concat("r", _ReferenceIndex)));
                joinRef.SearchConditions.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", _LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"), BinaryLogicOperator.Equals, new Column(null, String.Concat("r", _ReferenceIndex), "FK_Parent")));

                _EntityIndex++;
                JoinedTable joinEnt = new JoinedTable(joinRef, TypeJoinedTable.Inner, new TableSource(null, _Engine.EntityTableName, String.Concat("e", _EntityIndex)));
                joinEnt.SearchConditions.Add(new BinaryLogicExpression(new Column(null, String.Concat("r", _ReferenceIndex), "FK_Child"), BinaryLogicOperator.Equals, new Column(null, String.Concat("e", _EntityIndex), "Id")));

                _EntityJoinTable.Push(joinEnt);

                if (index != 0)
                    _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                _LinkEntityIndex.Add(_EntityIndex);

                result.WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("r", _ReferenceIndex), "Role"), BinaryLogicOperator.Equals, new Constant(ident.Value, DbType.AnsiString)));

                _ReferenceIndex++;
                _EntityType.Push(_Model.GetReference((string)_EntityType.Peek(), ident.Value, true).ChildType);

				if(ident.Constraint != null)
				{
                    ident.Constraint.Accept(this);

                    if(_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);

                    if (_ResultColumn != null)
                        ((SelectStatement)result).WhereClause.SearchCondition.Add((ILogicExpression)_ResultColumn);
				}

                if (!lastIsAttribute)
                {
                    if (index == (lastIndex - 1))
                        result.FromClause.Add((Table)_EntityJoinTable.Pop());
                }
                else
                    hasLinkedAttribute = true;
			}


            if(lastIsAttribute)
			{
				ident = path.Identifiers[index];

				if(ident.Constraint != null)
					throw new PersistenceEngineException("Constraint not allowed here");

                if (ident.Value == "Id")
                {
                     _ResultColumn = new Column(null, string.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id");
                }
                else
                {
                    //result.InnerJoinsAtrb.Add(
                    //    new InnerJoin(
                    //    "Attribute",
                    //    String.Format("a{0}", _AttributeIndex),
                    //    //String.Format("e{1}.Id = a{0}.FK_Entity", _AttributeIndex, currentIndex))
                    //    String.Format("e{1}.Id = a{0}.FK_Entity", _AttributeIndex, (int)_LinkEntityIndex.Peek()))
                    //    );

                    //result.Where.Add(String.Format("a{0}.Name = '{1}'", _AttributeIndex, ident.Value));
                    //result.text = String.Format("a{0}.[Value]", _AttributeIndex);

                    if (_EntityJoinTable.Count == 0)
                    {
                        _EntityIndex++;
                        _EntityJoinTable.Push(new TableSource(null, _Engine.EntityTableName, String.Concat("e", _EntityIndex)));
                        _LinkEntityIndex.Add(_EntityIndex);
                    }

                    JoinedTable joinAttr = new JoinedTable((Table) _EntityJoinTable.Pop(), 
                                                            TypeJoinedTable.LeftOuter,
                                                            new TableSource(null, _Engine.AttributeTableName, String.Concat("a", _AttributeIndex)));

                    joinAttr.SearchConditions.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id"),
                                                                         BinaryLogicOperator.Equals,
                                                                         new Column(null, String.Concat("a", _AttributeIndex), "FK_Entity")));

                    _EntityJoinTable.Push(joinAttr);
                    result.WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("a", _AttributeIndex), "Name"),
                                                                                                BinaryLogicOperator.Equals,
                                                                                                new Constant(ident.Value, DbType.AnsiString)));

                    _ResultColumn = new Column(null, string.Concat("a", (int)_AttributeIndex), SqlCommandProcessor.GetAttributeColumnNameFromDbType( SqlCommandProcessor.GetDbTypeFromType( Type.GetType(_Model.GetAttribute((string)_EntityType.Peek(), ident.Value, true).TypeName), _MappingDataType)));
                    _AttributeIndex++;
                }

                if (hasLinkedAttribute)
                    result.FromClause.Add((Table)_EntityJoinTable.Pop());
			}

            _EntityType = tmpEntityType;
            _ExprLevel.Pop();

			return result;
        }

        private ISQLExpression ConvertToSql(Call call, bool firstIsType, bool lastIsAttribute, bool isInConstraint)
        {
            _ExprLevel.Push(_ExprLevel.Peek());

            ISQLExpression result = null;

            if (call.Name == "eval")
            {
                call.Operands[0].Accept(this);
                result = (_Result == null) ? _ResultColumn : _Result;
            }

            _ExprLevel.Pop();

            return result;
        }

        #endregion

        #region Visit

        // Process constraints
		public override void Visit(Path path)
		{
            bool firstIsType = ((int)_ExprLevel.Peek() == EXPR);
            _ExprLevel.Push(PATH);

			_Result = ConvertToSql(path, firstIsType, _LastIsAttribute, true);

            if (path.Identifiers.Count > 0)
            {
                //  Remove last entity index if the attribute constraint concerns a Reference
                //  Person[Kind.Name = 'nice']
                Evaluant.Uss.Models.Entity e = _Model.GetEntity(path.Identifiers[0].Value);

                if (e == null && _LastIsAttribute && path.Identifiers.Count >= 2)
                {
                    //  Get the Reference Name
                    string type = (string) _EntityType.Peek();
                    string refName = _Model.GetReference(type, path.Identifiers[path.Identifiers.Count - 2].Value).Name;

                    if (path.Identifiers[path.Identifiers.Count - 2].Value == refName)
                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                }
            }
            _ExprLevel.Pop();
		}

		public override void Visit(Value val) 
		{
            switch(val.Type)
            {
                case ValueEnum.Boolean:
					_ResultColumn = new Constant(val.Text, (DbType)Enum.Parse(typeof(DbType), _MappingDataType.Bool.Name, true));
                    break;

                case ValueEnum.Date:
					_ResultColumn = new Constant(val.Text, (DbType)Enum.Parse(typeof(DbType), _MappingDataType.DateTime.Name, true));
                    break;

                case ValueEnum.Float:
					_ResultColumn = new Constant(val.Text, (DbType)Enum.Parse(typeof(DbType), _MappingDataType.Float.Name, true));
                    break;

                case ValueEnum.Integer:
					_ResultColumn = new Constant(val.Text, (DbType)Enum.Parse(typeof(DbType), _MappingDataType.Int.Name, true));
                    break;
                
                default:
					_ResultColumn = new Constant(val.Text, (DbType)Enum.Parse(typeof(DbType), _MappingDataType.String.Name, true));
                    break;
            }
		}

		public override void Visit(Function function) 
		{
            bool firstIsType = ((int)_ExprLevel.Peek() == EXPR);
            _ExprLevel.Push(_ExprLevel.Peek());

            Stack tmpEntityJoinTable = null;

			switch(function.Type)
			{
				case FunctionEnum.Average :
                    tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                    _EntityJoinTable.Clear();
                    
                    _Result = ConvertToSql(function.Path, firstIsType, true, true);

                    ((SelectStatement)_Result).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Avg, (Column)_ResultColumn));

                    if (!firstIsType)
                    {
                        if (_EntityJoinTable.Count > 0)
                            ((SelectStatement)_Result).FromClause.Add((Table)_EntityJoinTable.Pop());

                        if (function.Path.Identifiers.Count > 1)
                            _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                        ((SelectStatement)_Result).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"),
                                                                                                                    BinaryLogicOperator.Equals,
                                                                                                                    new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-2]), "Id")));
                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                    } 
                    _ResultColumn = _Result;
                    _Result = null;

                    _EntityJoinTable = tmpEntityJoinTable;
                    break;
				
				case FunctionEnum.Count :
                    if (firstIsType)
                    {
                        _Result = ConvertToSql(function.Path, firstIsType, false, true);

                        //  SELECT Count(Id)
                        //  FROM
                        //  (
                        //      SELECT DISTINCT Id
                        //      ...
                        //  ) as tbl

                        ((SelectStatement)_Result).SelectList.Add(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"));
                        ((SelectStatement)_Result).IsDistinct = true;
                        ((SelectStatement)_Result).TableAlias = "tbl";

                        SelectStatement selectCount = new SelectStatement(null);
                        selectCount.SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Count, new Column(null, "Id")));
                        selectCount.FromClause.Add(_Result as Table);

                        _ResultColumn = selectCount;
                        _Result = null;
                    }
                    else
                    {
                        tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                        _EntityJoinTable.Clear();

                        _Result = ConvertToSql(function.Path, firstIsType, false, true);

                        _EntityJoinTable = tmpEntityJoinTable;

                        _ResultColumn = new SelectStatement(null);
                        Merge((SelectStatement)_ResultColumn, (SelectStatement)_Result);

                        ((SelectStatement)_ResultColumn).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Count,
                                                                    new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id")));

                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                        ((SelectStatement)_ResultColumn).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"),
                                                                                                                    BinaryLogicOperator.Equals,
                                                                                                                    new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 2]), "Id")));
                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                        _Result = null;
                    }
					break;

				case FunctionEnum.Exists :
                    tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                    _EntityJoinTable.Clear();

                    _Result = ConvertToSql(function.Path, firstIsType, false, true);

                    _EntityJoinTable = tmpEntityJoinTable;

                    _ResultColumn = new SelectStatement(null);
                    Merge((SelectStatement)_ResultColumn, (SelectStatement)_Result);

                    ((SelectStatement)_ResultColumn).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Count,
                                                                new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id")));

                    _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                    ((SelectStatement)_ResultColumn).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id"),
                                                                                                                BinaryLogicOperator.Equals,
                                                                                                                new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-2]), "Id")));
                    
                    _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                    _Result = new SelectStatement(null);
                    _ResultColumn = new BinaryLogicExpression(_ResultColumn, BinaryLogicOperator.Greater, new Constant(0, DbType.Int32));
					break;

				case FunctionEnum.IsNull : // IsNull <=> not Exists
                    tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                    _EntityJoinTable.Clear();

                    _Result = ConvertToSql(function.Path, firstIsType, true, true);

                    _ResultColumn = new SelectStatement(null);
                    Merge((SelectStatement)_ResultColumn, (SelectStatement)_Result);

                    ((SelectStatement)_ResultColumn).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Count,
                                                                new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id")));

                    if(_EntityJoinTable.Count > 0)
                        ((SelectStatement)_ResultColumn).FromClause.Add((Table)_EntityJoinTable.Pop());

                    if(function.Path.Identifiers.Count > 1)
                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                    ((SelectStatement)_ResultColumn).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-1]), "Id"),
                                                                                                                BinaryLogicOperator.Equals,
                                                                                                                new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count-2]), "Id")));
                    _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                    _Result = new SelectStatement(null);
                    _ResultColumn = new BinaryLogicExpression(_ResultColumn, BinaryLogicOperator.Equals, new Constant(0, DbType.Int32));

                    _EntityJoinTable = tmpEntityJoinTable;
					break;

				case FunctionEnum.Max :
                    tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                    _EntityJoinTable.Clear();

                    _Result = ConvertToSql(function.Path, firstIsType, true, true);

                    ((SelectStatement)_Result).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Max,
                                                                        (Column)_ResultColumn));

                    if (!firstIsType)
                    {
                        if (_EntityJoinTable.Count > 0)
                            ((SelectStatement)_Result).FromClause.Add((Table)_EntityJoinTable.Pop());

                        if (function.Path.Identifiers.Count > 1)
                            _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                        ((SelectStatement)_Result).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"),
                                                                                                                    BinaryLogicOperator.Equals,
                                                                                                                    new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 2]), "Id")));

                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                    }
                    _ResultColumn = _Result;
                    _Result = null;

                    _EntityJoinTable = tmpEntityJoinTable;
                    break;

				case FunctionEnum.Min :
                    tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                    _EntityJoinTable.Clear();

                    _Result = ConvertToSql(function.Path, firstIsType, true, true);

                    ((SelectStatement)_Result).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Min,
                                                                        (Column)_ResultColumn));

                    if (!firstIsType)
                    {
                        if (_EntityJoinTable.Count > 0)
                            ((SelectStatement)_Result).FromClause.Add((Table)_EntityJoinTable.Pop());

                        if (function.Path.Identifiers.Count > 1)
                            _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                        ((SelectStatement)_Result).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"),
                                                                                                                    BinaryLogicOperator.Equals,
                                                                                                                    new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 2]), "Id")));

                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                    }
                    _ResultColumn = _Result;
                    _Result = null;

                    _EntityJoinTable = tmpEntityJoinTable;
                    break;

				case FunctionEnum.Sum :
                    tmpEntityJoinTable = (Stack)_EntityJoinTable.Clone();
                    _EntityJoinTable.Clear();

                    _Result = ConvertToSql(function.Path, firstIsType, true, true);

                    ((SelectStatement)_Result).SelectList.Add(new AggregateFunction(null, AggregateFunctionEnum.Sum,
                                                                        (Column)_ResultColumn));

                    if (!firstIsType)
                    {
                        if (_EntityJoinTable.Count > 0)
                            ((SelectStatement)_Result).FromClause.Add((Table)_EntityJoinTable.Pop());

                        if (function.Path.Identifiers.Count > 1)
                            _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);

                        ((SelectStatement)_Result).WhereClause.SearchCondition.Add(new BinaryLogicExpression(new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 1]), "Id"),
                                                                                                                    BinaryLogicOperator.Equals,
                                                                                                                    new Column(null, String.Concat("e", (int)_LinkEntityIndex[_LinkEntityIndex.Count - 2]), "Id")));

                        _LinkEntityIndex.RemoveAt(_LinkEntityIndex.Count - 1);
                    }
                    _ResultColumn = _Result;
                    _Result = null;

                    _EntityJoinTable = tmpEntityJoinTable;
                    break;
			}

            _ExprLevel.Pop();
		}

		public override void Visit(BinaryOperator binaryop) 
		{
            _ExprLevel.Push(_ExprLevel.Peek());

            SelectStatement result = new SelectStatement(null);
            ISQLExpression left = null;
            ISQLExpression right = null;

            switch (binaryop.Type)
            {
                case BinaryOperatorEnum.And:
                case BinaryOperatorEnum.Or:

                    _LastIsAttribute = true;
                    binaryop.LeftOperand.Accept(this);

                    if (_Result is SelectStatement)
                    {
                        SelectStatement select = (SelectStatement)_Result;
                        if (select.WhereClause.SearchCondition.Count > 0)
                        {
                            left = select.WhereClause.SearchCondition[0];
                            for (int i = 1; i < select.WhereClause.SearchCondition.Count; i++)
                                left = new BinaryLogicExpression(left, BinaryLogicOperator.And, select.WhereClause.SearchCondition[i]);
                        }

                        ((SelectStatement)result).FromClause.AddRange(select.FromClause);
                    }

                    if (_ResultColumn != null)
                    {
                        left = (left == null) ? _ResultColumn : new BinaryLogicExpression(left, BinaryLogicOperator.And, _ResultColumn);
                    }

                    
                    _ResultColumn = null;
                    _Result = null;

                    _LastIsAttribute = true;
                    binaryop.RightOperand.Accept(this);

                    if (_Result is SelectStatement)
                    {
                        SelectStatement select = (SelectStatement)_Result;
                        if (select.WhereClause.SearchCondition.Count > 0)
                        {
                            right = select.WhereClause.SearchCondition[0];
                            for (int i = 1; i < select.WhereClause.SearchCondition.Count; i++)
                                right = new BinaryLogicExpression(right, BinaryLogicOperator.And, select.WhereClause.SearchCondition[i]);
                        }

                        ((SelectStatement)result).FromClause.AddRange(select.FromClause);
                    }
                    
                    if (_ResultColumn != null)
                    {
                        right = (right == null) ? _ResultColumn : new BinaryLogicExpression(right, BinaryLogicOperator.And, _ResultColumn);
                    }

                    
                    _Result = null;
                    _ResultColumn = new BinaryLogicExpression(left, BinaryOperatorToBinaryLogicOperator(binaryop.Type), right);
                    break;

                case BinaryOperatorEnum.Equal:
                case BinaryOperatorEnum.Greater:
                case BinaryOperatorEnum.GreaterOrEqual:
                case BinaryOperatorEnum.Lesser:
                case BinaryOperatorEnum.LesserOrEqual:
                case BinaryOperatorEnum.NotEqual:

                    //_LastIsAttribute = true;
                    //binaryop.LeftOperand.Accept(this);
                    //left = _Result;
                    //result.Merge(left);

                    //_LastIsAttribute = true;
                    //binaryop.RightOperand.Accept(this);
                    //right = _Result;
                    //result.Merge(right);

                    //if (binaryop.LeftOperand is Value && !(binaryop.RightOperand is Function))
                    //{
                    //    left.text = "'" + FormatValue((Value)binaryop.LeftOperand) + "'";
                    //}

                    //if (binaryop.RightOperand is Value && !(binaryop.LeftOperand is Function))
                    //{
                    //    right.text = "'" + FormatValue((Value)binaryop.RightOperand) + "'";
                    //}
                    //result.Where.Add(left.text + BinaryOperatorToString(binaryop.Type) + right.text);

                    _LastIsAttribute = true;
                    binaryop.LeftOperand.Accept(this);
                    left = _ResultColumn;
                    if(_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;

                    _LastIsAttribute = true;
                    binaryop.RightOperand.Accept(this);
                    right = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;
                    _ResultColumn = null;

                    BinaryLogicExpression exp = new BinaryLogicExpression(left, BinaryOperatorToBinaryLogicOperator(binaryop.Type), right);

                    if (_FromUnaryOperator)
                        _ResultColumn = exp;
                    else
                        result.WhereClause.SearchCondition.Add(exp);
                   
                    break;


                case BinaryOperatorEnum.Div:
                case BinaryOperatorEnum.Minus:
                case BinaryOperatorEnum.Modulo:
                case BinaryOperatorEnum.Plus:
                case BinaryOperatorEnum.Times:

                    if ((int)_ExprLevel.Peek() == EXPR)
                    {
                        _LastIsAttribute = true;
                        binaryop.LeftOperand.Accept(this);
                        left = (_Result is SelectStatement) ? _Result : _ResultColumn;
                        _Result = null;

                        _LastIsAttribute = true;
                        binaryop.RightOperand.Accept(this);
                        right = (_Result is SelectStatement) ? _Result : _ResultColumn;
                        
                        result.SelectList.Add( new BinaryLogicExpression(left, BinaryOperatorToBinaryLogicOperator(binaryop.Type), right));

                    }
                    else
                    {
                        _LastIsAttribute = true;
                        binaryop.LeftOperand.Accept(this);
                        left = _ResultColumn;
                        if (_Result is SelectStatement)
                            Merge((SelectStatement)result, (SelectStatement)_Result);
                        _Result = null;

                        _LastIsAttribute = true;
                        binaryop.RightOperand.Accept(this);
                        right = _ResultColumn;
                        if (_Result is SelectStatement)
                            Merge((SelectStatement)result, (SelectStatement)_Result);
                        _Result = null;

                        result.WhereClause.SearchCondition.Add(new BinaryLogicExpression(left, BinaryOperatorToBinaryLogicOperator(binaryop.Type), right));
                    }

                    break;

                case BinaryOperatorEnum.Contains:

                    _LastIsAttribute = true;
                    binaryop.LeftOperand.Accept(this);
                    left = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;

                    _LastIsAttribute = true;
                    binaryop.RightOperand.Accept(this);
                    right = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;
                    _ResultColumn = null;

                    result.WhereClause.SearchCondition.Add(new LikePredicate(left, String.Format("%{0}%", (string)((Constant)right).Value)));

                    break;

                case BinaryOperatorEnum.BeginsWith:

                    //_LastIsAttribute = true;
                    //binaryop.LeftOperand.Accept(this);
                    //left = _Result;
                    //result.Merge(left);

                    //_LastIsAttribute = true;
                    //binaryop.RightOperand.Accept(this);
                    //right = _Result;
                    //result.Merge(right);

                    //if (binaryop.LeftOperand is Value && !(binaryop.RightOperand is Function))
                    //{
                    //    left.text = "'" + FormatValue((Value)binaryop.LeftOperand) + "%'";
                    //}

                    //if (binaryop.RightOperand is Value && !(binaryop.LeftOperand is Function))
                    //{
                    //    right.text = "'" + FormatValue((Value)binaryop.RightOperand) + "%'";
                    //}
                    //result.Where.Add(left.text + BinaryOperatorToString(binaryop.Type) + right.text);


                    _LastIsAttribute = true;
                    binaryop.LeftOperand.Accept(this);
                    left = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;

                    _LastIsAttribute = true;
                    binaryop.RightOperand.Accept(this);
                    right = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;
                    _ResultColumn = null;

                    result.WhereClause.SearchCondition.Add(new LikePredicate(left, String.Format("{0}%", (string)((Constant)right).Value)));
                    break;

                case BinaryOperatorEnum.EndsWith:
                    //_LastIsAttribute = true;
                    //binaryop.LeftOperand.Accept(this);
                    //left = _Result;
                    //result.Merge(left);

                    //_LastIsAttribute = true;
                    //binaryop.RightOperand.Accept(this);
                    //right = _Result;
                    //result.Merge(right);

                    //if (binaryop.LeftOperand is Value && !(binaryop.RightOperand is Function))
                    //{
                    //    left.text = "'%" + FormatValue((Value)binaryop.LeftOperand) + "'";
                    //}

                    //if (binaryop.RightOperand is Value && !(binaryop.LeftOperand is Function))
                    //{
                    //    right.text = "'%" + FormatValue((Value)binaryop.RightOperand) + "'";
                    //}
                    //result.Where.Add(left.text + BinaryOperatorToString(binaryop.Type) + right.text);
                    _LastIsAttribute = true;
                    binaryop.LeftOperand.Accept(this);
                    left = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;

                    _LastIsAttribute = true;
                    binaryop.RightOperand.Accept(this);
                    right = _ResultColumn;
                    if (_Result is SelectStatement)
                        Merge((SelectStatement)result, (SelectStatement)_Result);
                    _Result = null;
                    _ResultColumn = null;

                    result.WhereClause.SearchCondition.Add(new LikePredicate(left, String.Format("%{0}", (string)((Constant)right).Value)));

                    break;
            }

            _Result = result;

            _ExprLevel.Pop();
		}

        private bool _FromUnaryOperator;

		public override void Visit(UnaryOperator unaryop) 
		{
            try
            {
                _FromUnaryOperator = true;

                unaryop.Operand.Accept(this);
            }
            finally
            {
                _FromUnaryOperator = false;
            }

            UnaryLogicOperator opType = UnaryLogicOperator.Not;
            switch(unaryop.Type)
            {
                case UnaryOperatorEnum.Minus: opType = UnaryLogicOperator.Minus; break;
                case UnaryOperatorEnum.Not: opType = UnaryLogicOperator.Not; break;
                case UnaryOperatorEnum.Unknown: opType = UnaryLogicOperator.Unknown; break;
            }

            _ResultColumn = new UnaryLogicExpression(opType, _ResultColumn);
            _Result = null;
		}

		public override void Visit(Identifier identifier) { throw new NotImplementedException(); }

		public override void Visit(Call call) 
		{ 
			switch(call.Name)
			{
				case "id" :

                    //ArrayList ids = new ArrayList();
                    //foreach(OPath.Grammar.Constraint op in call.Operands)
                    //{
                    //    op.Accept(this);
                    //    ids.Add(_Result.text);
                    //}
                    //_Result.text = String.Format(" e{0}.Id in ('{1}') ", _EntityIndex - 1, String.Join("', '", (string[])ids.ToArray(typeof(string))));

                    ExpressionCollection ids = new ExpressionCollection();
                    foreach (Evaluant.OPath.Expressions.Constraint op in call.Operands)
                    {
                        op.Accept(this);
                        ids.Add(_ResultColumn);
                    }
                    _ResultColumn = new InPredicate(new Column(null, String.Concat("e", _EntityIndex), "Id"), ids);

					break;
			}
        }

        #endregion

        #region Functions

        private SelectStatement Merge(SelectStatement s1, SelectStatement s2)
        {
            s1.SelectList.AddRange(s2.SelectList);
            
            s1.FromClause.AddRange(s2.FromClause);
            
            s1.WhereClause.SearchCondition.AddRange(s2.WhereClause.SearchCondition);
            return s1;
        }

        private static BinaryLogicOperator BinaryOperatorToBinaryLogicOperator(BinaryOperatorEnum binaryop)
        {
            switch (binaryop)
            {
                case BinaryOperatorEnum.And: return BinaryLogicOperator.And;
                case BinaryOperatorEnum.Div: return BinaryLogicOperator.Div;
                case BinaryOperatorEnum.Equal: return BinaryLogicOperator.Equals;
                case BinaryOperatorEnum.Greater: return BinaryLogicOperator.Greater;
                case BinaryOperatorEnum.GreaterOrEqual: return BinaryLogicOperator.GreaterOrEquals;
                case BinaryOperatorEnum.Lesser: return BinaryLogicOperator.Lesser;
                case BinaryOperatorEnum.LesserOrEqual: return BinaryLogicOperator.LesserOrEquals;
                case BinaryOperatorEnum.Minus: return BinaryLogicOperator.Minus;
                case BinaryOperatorEnum.Modulo: return BinaryLogicOperator.Modulo;
                case BinaryOperatorEnum.NotEqual: return BinaryLogicOperator.NotEquals;
                case BinaryOperatorEnum.Or: return BinaryLogicOperator.Or;
                case BinaryOperatorEnum.Plus: return BinaryLogicOperator.Plus;
                case BinaryOperatorEnum.Times: return BinaryLogicOperator.Times;
            }

            throw new PersistenceEngineException("Operator not managed: " + binaryop.ToString());
        }

        #endregion
    }
}
